package com.flextribe.display {

	import com.flextribe.CasaUtilsTmp;
	import com.flextribe.events.TribeGraphicsEvent;
	import com.flextribe.events.TribeSpriteEvent;
	import com.flextribe.math.TribeMath;
	
	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	/**
	 *  Dispatched when the position of the TribeSprite changes.
	 *
	 *  @eventType com.flextribe.events.TribeSpriteEvent.POSITION_CHANGE
	 */
	[Event(name="positionChange", type="com.flextribe.events.TribeSpriteEvent")]

	/**
	 *  Dispatched when the rotation of the TribeSprite changes.
	 *
	 *  @eventType com.flextribe.events.TribeSpriteEvent.ROTATION_CHANGE
	 */
	[Event(name="rotationChange", type="com.flextribe.events.TribeSpriteEvent")]
	
	/**
	 *  Dispatched when the size of the TribeSprite changes.
	 *
	 *  @eventType com.flextribe.events.TribeSpriteEvent.SIZE_CHANGE
	 */
	[Event(name="sizeChange", type="com.flextribe.events.TribeSpriteEvent")]

	/**
	 *  Dispatched when the graphics object changes.
	 *
	 *  @eventType com.flextribe.events.TribeGraphicsEvent.GRAPHICS_CHANGE
	 */
	[Event(name="graphicsChange", type="com.flextribe.events.TribeGraphicsEvent")]

	/**
	 * 
	 * @author llozan
	 * 
	 */
	public class TribeSprite extends Sprite {

		/**
		 * Set the timeout between doubleclicks. Works if custom doubleclick is enabled
		 */ 
		public static var doubleClickTimeout:int = 200;

		/**
		 * Sets the current align mode. Works in pair with autoAlign parameter. If <code>autoAlign</code> 
		 * is <code>false</code> it runs a single time, if <code>autoAlign</code> is <code>true</code> 
		 * the sprite's position is autoupdate each time any of the reference DO's <code>widht</code>, 
		 * <code>height</code>, <code>x</code> or <code>y</code> change.
		 */
		public function get align():String  {
			return this._align;
		}
		public function set align(value:String):void  {
			this._align = value;
			this.autoAlign = value != TribeSpriteAlign.NONE;
			this.updateDisplayList();
		}
		private var _align:String = "";

		/**
		 * The horizontal offset used in the alignment.
		 */ 
		public function get alignOffsetH():Number  {
			return this._alignOffsetH;
		}
		public function set alignOffsetH(value:Number):void  {
			this._alignOffsetH = value;
			this.updateDisplayList();
		}
		private var _alignOffsetH:Number = 0;

		/**
		 * The vertical offset used in the alignment.
		 */
		public function get alignOffsetV():Number  {
			return this._alignOffsetV;
		}
		public function set alignOffsetV(value:Number):void  {
			this._alignOffsetV = value;
			this.updateDisplayList();
		}
		private var _alignOffsetV:Number = 0;

		public function get alignToTopValue():Number{
			return _roundPosFunction( _alignOffsetV );
		}
		public function get alignToMiddleValue():Number{
			return _roundPosFunction( ( _referenceDO.height - this.height )/2 + _alignOffsetV );
		}
		public function get alignToBottomValue():Number{
			return _roundPosFunction( _referenceDO.height - this.height + _alignOffsetV );
		}
		public function get alignToLeftValue():Number{
			return _roundPosFunction( _alignOffsetH );
		}
		public function get alignToCenterValue():Number{
			return _roundPosFunction( (_referenceDO.width - this.width )/2 + _alignOffsetH );
		}
		public function get alignToRightValue():Number{
			return _roundPosFunction( _referenceDO.width - this.width + _alignOffsetH );
		}

		/**
		 * Specifies whether to update the position of the every time the <code>referenceDO</code> 
		 * changes size/position.
		 */
		public function get autoAlign():Boolean  {
			return this._autoAlign;
		}
		public function set autoAlign(value:Boolean):void  {
			this._autoAlign = value;
			if(_referenceDO){
				if(this._autoAlign){
					_referenceDO.addEventListener(Event.RENDER, updateDisplayList, false, 0, true);
				} else {
					_referenceDO.removeEventListener(Event.RENDER, updateDisplayList);
				}
			}
		}
		private var _autoAlign:Boolean = false;

		/**
		 * Sets the enabled status.
		 */ 
		public function set enabled(value:Boolean):void{
			_enabled = value;	
		}
		public function get enabled():Boolean{
			return _enabled;
		}
		private var _enabled:Boolean = true;

		/**
		 * 
		 */ 
		public override function get graphics():Graphics{
			throw new Error("To access TribeSprite graphics property, use graphic instead.");
			return null;
		} 
		/**
		 * 
		 */ 
		public function get tribeGraphics():TribeGraphics {
			return _tribeGraphics;
		}
		private var _tribeGraphics:TribeGraphics;

		/**
		 * A rectangle which stores the last position modifications applied to this display object.
		 * x and y represent the last position before the move, while width width and height represent the differences between the current and last positions.
		 */
		public function get lastMove():Rectangle  {
			return this._lastMove;
		}
		public function set lastMove(value:Rectangle):void  {
			this._lastMove = value;
		}
		private var _lastMove:Rectangle = new Rectangle(0, 0, 0, 0);
		
		/**
		 * Stores the next move. Is reset after applying
		 */
		private var _nextMove:Rectangle = null;
		
		/**
		 * Stores the angle of the next rotation.
		 */ 
		private var _nextRotation:Number = 0;
		private var _nextSize:Rectangle = null;

		/**
		 * The reference Display Object to whom this Display Object aligns. By default this is 
		 * the parent of the object.
		 */
		public function get referenceDO():DisplayObject  {
			return this._referenceDO;
		}
		public function set referenceDO(value:DisplayObject):void  {
			if(_referenceDO != null && _referenceDO.hasEventListener(Event.RENDER))
				_referenceDO.removeEventListener(Event.RENDER, updateDisplayList);
			this._referenceDO = value;
			this.autoAlign = this._autoAlign;
		}
		private var _referenceDO:DisplayObject = null;

		/**
		 * Override the default registration point (origin) of the current object. When you set 
		 * a new registration point the object's x and y coordinates update to correspond to it.
		 */
		public function get registrationPoint():Point  {
			return this._registrationPoint;
		}
		public function set registrationPoint(value:Point):void  {
			throw new Error("Feature not implemented yet.");
			this._registrationPoint = value;
		}
		private var _registrationPoint:Point = new Point(0, 0);

		/**
		 * Specifies whether to round x and y values of the display object.
		 */
		public function get roundPosition():Boolean  {
			return this._roundPosition;
		}
		public function set roundPosition(value:Boolean):void  {
			this._roundPosition = value;
			this._roundPosFunction = this._roundPosition ? Math.round : TribeMath.unmodifiedNumber;
		}
		private var _roundPosition:Boolean = false;
		private var _roundPosFunction:Function = TribeMath.unmodifiedNumber;

		/**
		 * Specifies whether to round width and height values of the display object.
		 */
		public function get roundSize():Boolean  {
			return this._roundSize;
		}
		public function set roundSize(value:Boolean):void  {
			this._roundSize = value;
			this._roundSizeFunction = this._roundSize ? Math.round : TribeMath.unmodifiedNumber;
		}
		private var _roundSize:Boolean = false;
		private var _roundSizeFunction:Function = TribeMath.unmodifiedNumber;

		/**
		 * Sets the width of the object.
		 */
		override public function get width():Number {
			return this._width;
		}
		override public function set width(value:Number):void  {
			this._width = value;
			this.updateDisplayList();
		}
		protected var _width:Number = 0;

		/**
		 * Sets the height of the object.
		 */
		override public function get height():Number {
			return this._height;
		}
		override public function set height(value:Number):void  {
			this._height = value;
			this.updateDisplayList();
		}
		protected var _height:Number = 0;

		/**
		 * Constructor of the TribeSprite
		 */
		public function TribeSprite(autoCreateChildren:Boolean = true) {
			super();
			this.addEventListener(Event.ADDED, addedHandler);
			if(autoCreateChildren) this.createChildren();
		}
		/**
		 * Sets the reference DO to the parent object upon first creation.
		 */
		private function addedHandler(event:Event):void{
			if(event.target == this) {
				this.referenceDO = this.parent;
				this.removeEventListener(Event.ADDED, addedHandler);
				realCoords = new Point(this.x, this.y);
			}
		}
		private function graphicsChangeHandler(event:TribeGraphicsEvent):void{
			this._width = this.tribeGraphics.width;
			this._height = this.tribeGraphics.height;
			this.updateDisplayList();
			this.dispatchEvent(event);
		}

		/**
		 * Override this method in subclasses. It will be automatically called from the constructor 
		 * (unless otherwise specified).
		 */
		public function createChildren():void {
			_tribeGraphics = new TribeGraphics();
			this.addChild(_tribeGraphics);
			_tribeGraphics.addEventListener(TribeGraphicsEvent.GRAPHICS_CHANGE, graphicsChangeHandler, false, 0, true);
		}

		/**
		 * Move object relatively to its current position.
		 * Warning! Using this method sets align value to "" and autoAlign to <code>false</code>.
		 */
		public function move(horizontalMove:Number, verticalMove:Number):void {
			if(_roundPosFunction(horizontalMove) == 0 && _roundPosFunction(verticalMove) == 0) return;
			_nextMove = new Rectangle(0, 0, _roundPosFunction(horizontalMove), _roundPosFunction (verticalMove) );
			updateDisplayList();
			dispatchEvent(new TribeSpriteEvent(TribeSpriteEvent.POSITION_CHANGE));
		}

		/**
		 * Set object position.
		 * Warning! Using this method sets align value to "" and autoAlign to <code>false</code>.
		 */
		public function place(x:Number, y:Number):void {
			if(this.x == _roundPosFunction(x) && this.y == _roundPosFunction(y)) return;
			_nextMove = new Rectangle(_roundPosFunction(x), _roundPosFunction(y), 0, 0);
			updateDisplayList();
			dispatchEvent(new TribeSpriteEvent(TribeSpriteEvent.POSITION_CHANGE));
		}

		/**
		 * Rotate object relatively to its current rotation angle.
		 */
		public function rotate(angle:Number):void {
			if(angle == 0) return;
			_nextRotation = angle;
			updateDisplayList();
			dispatchEvent(new TribeSpriteEvent(TribeSpriteEvent.ROTATION_CHANGE));
		}

		/**
		 * Set object width and height.
		 */
		public function setSize(width:Number, height:Number):void {
			_nextSize = new Rectangle(0, 0, width/this.width, height/this.height);
			updateDisplayList();
			dispatchEvent(new TribeSpriteEvent(TribeSpriteEvent.SIZE_CHANGE));
		}

		/**
		 * Set object scale.
		 */
		public function setScale(scaleX:Number, scaleY:Number):void {
			_nextSize = new Rectangle(0, 0, scaleX, scaleY);
			dispatchEvent(new TribeSpriteEvent(TribeSpriteEvent.SIZE_CHANGE));
		}

		/**
		 * Sends the object in front of all its siblings.
		 */
		public function orderToFront():Boolean{
			if(!parent.contains(this)) return false;
			if(parent.getChildIndex(this) == parent.numChildren - 1) return false;
			parent.setChildIndex(this, parent.numChildren - 1);	return true;
		}
		
		/**
		 * Sends the object under all its siblings.
		 */
		public function orderToBack():Boolean{
			if(!parent.contains(this)) return false;
			if(parent.getChildIndex(this) == 0) return false;
			parent.setChildIndex(this, 0); return true;
		}
		
		/**
		 * Promotes the object one layer up.
		 */
		public function orderForward():Boolean{
			if(!parent.contains(this)) return false;
			if(parent.getChildIndex(this) == parent.numChildren - 1) return false;
			parent.setChildIndex(this, parent.getChildIndex(this)+1); return true;
		}
		
		/**
		 * Sends the object one layer down.
		 */
		public function orderBackward():Boolean{
			if(!parent.contains(this)) return false;
			if(parent.getChildIndex(this) == parent.numChildren - 1) return false;
			parent.setChildIndex(this, parent.getChildIndex(this)-1); return true;
		}


		/**
		 * Updates the display list. Method is called everytime the size of the object changes. 
		 * Override this method to implement additional functionality.
		 */
		private var realCoords:Point = new Point();
		public function updateDisplayList(event:Event=null):void {
			//trace(this.parent);
			//if(this.parent == null) return;
			var finalRect:Rectangle = new Rectangle(realCoords.x, realCoords.y, this.scaleX, this.scaleY);
			
			if(_nextSize != null){
				finalRect.width = _nextSize.width;
				finalRect.height = _nextSize.height;
				_nextSize = null;
			}
			if(_nextMove != null){
				if(_nextMove.width != 0 || _nextMove.height != 0){
					finalRect.x += _nextMove.width;
					finalRect.y += _nextMove.height;
				} else {
					finalRect.x = _nextMove.x;
					finalRect.y = _nextMove.y;
				}
				_nextMove = null;
				_autoAlign = false;
				_align = TribeSpriteAlign.NONE;
			} else if(_align != TribeSpriteAlign.NONE){
				switch(_align.toUpperCase()){
					// mono-directional alignment
					case TribeSpriteAlign.LEFT:				finalRect.x = alignToLeftValue;		break;
					case TribeSpriteAlign.CENTER:			finalRect.x = alignToCenterValue;	break;
					case TribeSpriteAlign.RIGHT:			finalRect.x = alignToRightValue;	break;
					case TribeSpriteAlign.TOP:				finalRect.y = alignToTopValue;		break;
					case TribeSpriteAlign.MIDDLE:			finalRect.y = alignToMiddleValue;	break;
					case TribeSpriteAlign.BOTTOM:			finalRect.y = alignToBottomValue;	break;					
					// bidirectional alignment
					case TribeSpriteAlign.TOP_LEFT:			finalRect.x = alignToLeftValue;		finalRect.y = alignToTopValue; 		break;
					case TribeSpriteAlign.TOP_CENTER:		finalRect.x = alignToCenterValue;	finalRect.y = alignToTopValue;		break;
					case TribeSpriteAlign.TOP_RIGHT:		finalRect.x = alignToRightValue;	finalRect.y = alignToTopValue;		break;
					case TribeSpriteAlign.MIDDLE_LEFT:		finalRect.x = alignToLeftValue;		finalRect.y = alignToMiddleValue;	break;
					case TribeSpriteAlign.MIDDLE_CENTER:	finalRect.x = alignToCenterValue;	finalRect.y = alignToMiddleValue; 	break;
					case TribeSpriteAlign.MIDDLE_RIGHT:		finalRect.x = alignToRightValue;	finalRect.y = alignToMiddleValue;	break;
					case TribeSpriteAlign.BOTTOM_LEFT: 		finalRect.x = alignToLeftValue;		finalRect.y = alignToBottomValue;	break;
					case TribeSpriteAlign.BOTTOM_CENTER:	finalRect.x = alignToCenterValue;	finalRect.y = alignToBottomValue;	break;
					case TribeSpriteAlign.BOTTOM_RIGHT:		finalRect.x = alignToRightValue;	finalRect.y = alignToBottomValue;	break;
					default:								break;
				}
			}
		
			realCoords.x = finalRect.x;
			realCoords.y = finalRect.y;
			
			if(_nextRotation != 0){
				this.rotation += _nextRotation;
				_nextRotation = 0;
			}

			var coords:Point = new Point(finalRect.x, finalRect.y);
			var center:Point = new Point(finalRect.x + this.width/2, finalRect.y + this.height/2);
			CasaUtilsTmp.rotatePoint(coords, center, this.rotation + _nextRotation);
			finalRect.x = coords.x;
			finalRect.y = coords.y;

			this.x = finalRect.x;
			this.y = finalRect.y;
			//super.scaleX = finalRect.width;
			//super.scaleY = finalRect.height;
			//super.width = _roundSizeFunction(super.width);
			//super.height = _roundSizeFunction(super.height);
		}

		/**
		 * Override this method and implement self-destruction operations for the object (unregister 
		 * listeners, destroy timers etc.).
		 */
		public function destroy():void {
			_tribeGraphics.removeEventListener(TribeGraphicsEvent.GRAPHICS_CHANGE, graphicsChangeHandler); 
			this.removeChild(_tribeGraphics);
			_tribeGraphics = null;
		}

	}
}